package tool

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/op/go-logging"
	"io"
	"net"
	"net/textproto"
	"net/url"
	"os"
	"strings"
	"time"
)

type Conn struct {
	server *Server
	conn   net.Conn
	buf    *bufio.Reader
}

var connLogger *logging.Logger = logging.MustGetLogger("Conn")
var format = logging.MustStringFormatter(
	`%{color}%{time:15:04:05.000} %{longfile} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
)

func init() {
	backend1 := logging.NewLogBackend(os.Stdout, "", 0)
	backend2 := logging.NewLogBackend(os.Stdout, "", 0)

	// For messages written to backend2 we want to add some additional
	// information to the output, including the used log level and the name of
	// the function.
	backend2Formatter := logging.NewBackendFormatter(backend2, format)

	// Only errors and more severe messages should be sent to backend1
	backend1Leveled := logging.AddModuleLevel(backend1)
	backend1Leveled.SetLevel(logging.ERROR, "")
	logging.SetBackend(backend1Leveled, backend2Formatter)

}

func makeTunnelRequestLine(addr, ua string) string {
	return fmt.Sprintf("CONNECT %s HTTP/1.1\r\nProxy-Authorization: %s\r\n\r\n", addr, ua)
}
func (c *Conn) serve() {
	defer c.conn.Close()
	rawHttpRequestHeader, remote, credential, requestLine, isHttps, err := c.getTunnelInfo()

	if err != nil {
		connLogger.Error(err)
		return
	}
	fmt.Println("requestLine", requestLine)
	if c.auth(credential) == false {
		connLogger.Error("Auth fail: " + credential)
		return
	}
	fmt.Println(rawHttpRequestHeader.String())
	var remoteConn net.Conn
	if c.server.upstream != nil {
		// 像上游代理发送
		connLogger.Info("打开上游代理", c.server.upstream.host)
		conn, err := net.DialTimeout("tcp", c.server.upstream.host, time.Second*10)
		if err != nil {
			connLogger.Error("上游代理失败", err)
		}
		c.server.upstream.conn = conn
		remoteConn = c.server.upstream.conn
		//if !isHttps {
		//	// if https, should sent 200 to client
		//	_, err = c.conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
		//	if err != nil {
		//		connLogger.Error(err)
		//		return
		//	}
		//} else {
		// if not https, should sent the request header to remote
		//if k == "Proxy-Authorization" {
		if !isHttps {
			connLogger.Info("不是https", 1, rawHttpRequestHeader.String())
			_, err = rawHttpRequestHeader.WriteTo(remoteConn)
			if err != nil {
				connLogger.Error(err)
				return
			}
		} else {
			_, err = rawHttpRequestHeader.WriteTo(remoteConn)
			if err != nil {
				connLogger.Error(err)
				return
			}
		}

		//}
		//}
	} else {
		connLogger.Info("connecting to " + remote)
		remoteConn, err = net.Dial("tcp", remote)
		if err != nil {
			connLogger.Error(err)
			return
		}

		if isHttps {
			// if https, should sent 200 to client
			_, err = c.conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
			if err != nil {
				connLogger.Error(err)
				return
			}
		} else {
			// if not https, should sent the request header to remote
			connLogger.Info("不是https", 1, rawHttpRequestHeader.String())
			_, err = rawHttpRequestHeader.WriteTo(remoteConn)
			if err != nil {
				connLogger.Error(err)
				return
			}
		}
	}

	// build bidirectional-streams
	connLogger.Info("begin tunnel", c.conn.RemoteAddr(), "<->", remote)
	c.tunnel(remoteConn, isHttps)
	connLogger.Info("stop tunnel", c.conn.RemoteAddr(), "<->", remote)
}

func (c *Conn) tunnel(remoteConn net.Conn, isHttps bool) {
	defer remoteConn.Close()

	go func() {
		connLogger.Info("写入数据", c.buf.Size())
		_, err := c.buf.WriteTo(remoteConn)
		if err != nil {
			connLogger.Warning(err)
		}
	}()
	connLogger.Info("开始双向拷贝")

	_, err := io.Copy(c.conn, remoteConn)
	if err != nil {
		connLogger.Warning(err)
	}
}

func (c *Conn) getTunnelInfo() (rawReqHeader bytes.Buffer, host, credential, requestLine string, isHttps bool, err error) {
	tp := textproto.NewReader(c.buf)

	if requestLine, err = tp.ReadLine(); err != nil {
		return
	}
	method, requestURI, _, ok := parseRequestLine(requestLine)
	if !ok {
		err = errors.New("malformed HTTP request")
		return
	}

	// https request
	if method == "CONNECT" {
		isHttps = true
		requestURI = "http://" + requestURI
	}

	// get remote host
	uriInfo, err := url.ParseRequestURI(requestURI)
	if err != nil {
		return
	}

	// Subsequent lines: Key: value.
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil {
		return
	}

	credential = mimeHeader.Get("Proxy-Authorization")

	if uriInfo.Host == "" {
		host = mimeHeader.Get("Host")
	} else {
		if strings.Index(uriInfo.Host, ":") == -1 {
			host = uriInfo.Host + ":80"
		} else {
			host = uriInfo.Host
		}
	}

	// rebuild http request header
	fmt.Println("requestLine", requestLine)
	rawReqHeader.WriteString(requestLine + "\r\n")
	for k, vs := range mimeHeader {
		for _, v := range vs {
			if c.server.upstream != nil {
				fmt.Println(fmt.Sprintf("Basic %s", c.server.upstream.ua))
				if k == "Proxy-Authorization" {
					v = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(c.server.upstream.ua)))
				}
			}

			rawReqHeader.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
		}
	}
	if c.server.upstream != nil && c.server.upstream.ua != "" {
		k := "Proxy-Authorization"
		v := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(c.server.upstream.ua)))
		rawReqHeader.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))

	}
	rawReqHeader.WriteString("\r\n\r\n")
	return
}

// auth provide basic authentication
func (c *Conn) auth(credential string) bool {
	if c.server.isAuth() == false || c.server.validateCredential(credential) {
		return true
	}
	// 407
	_, err := c.conn.Write(
		[]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"*\"\r\n\r\n"))
	if err != nil {
		connLogger.Error(err)
	}
	return false
}

func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
	s1 := strings.Index(line, " ")
	s2 := strings.Index(line[s1+1:], " ")
	if s1 < 0 || s2 < 0 {
		return
	}
	s2 += s1 + 1
	return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
